import java.util.LinkedList;
import java.util.Queue;

/*
 * TCSS 422 Process Control Simulator
 * Group Money: Al McKenzie, Michael Pitts, Taylor Zielske
 */

/**
 * A CPU which runs Processes and accepts Interrupts.
 * 
 * @author Michael Pitts
 * @version Dec 8, 2011
 */
public class CPU {
	
	/** The process scheduler for this CPU. */
	private final Scheduler my_scheduler;
	
	/** The Queue of Interrupts waiting on this CPU. */
	private final Queue<Interrupt> my_io_interrupts;
	
	/** How many cycles between Timer Interrupts. */
	private final int my_timer_switch_iterations;
	
	/** If true, IO Interrupts allow IO requesting Process immediate run, otherwise
	 * requesting process has to wait. */
	private final boolean my_IO_unbuffered;
	
	/** How long until the next Timer Interrupt. */
	private int my_timer_left;
	
	/** The current Process trying to be run. */
	private Process my_current_process;
	
	/** If true indicates a time interrupt was received. */
	private boolean my_timer_interrupt;
	
	/**
	 * Constructs a new CPU, given an array of Processes to run.
	 * @param the_scheduler is the scheduler that will be used for the new CPU.
	 * @param the_switch_iter is how many cycles there are between Timer Interrupts.
	 * @param the_IO_unbuffered if true, IO Interrupts immediately switch to the requesting
	 * process, otherwise IO requesters have to wait normally.
	 */
	public CPU(final Scheduler the_scheduler, final int the_switch_iter, 
			final boolean the_IO_unbuffered) {
		my_scheduler = the_scheduler;
		my_io_interrupts = new LinkedList<Interrupt>();
		my_timer_switch_iterations = the_switch_iter;
		my_timer_left = the_switch_iter;
		my_IO_unbuffered = the_IO_unbuffered;
	}
	
	/**
	 * Loads the given processes into the CPU to run.
	 * @param the_processes are the Processes to run, in starting order.
	 */
	public void loadProcesses(final Process[] the_processes) {
		my_scheduler.loadProcesses(the_processes);
		my_current_process = my_scheduler.getNextProcess();
	}
	
	/**
	 * Runs one iteration of the current Process.
	 */
	public void runOneCycle() {
		timerRunOne();
		final Interrupt interrupt = peekInterrupt();
		if (my_timer_interrupt) {
			my_timer_interrupt = false;
			switchProcess(null);
		} else if (interrupt != null  && interrupt.getType() == Interrupt.IO){
			doIO(interrupt);
		} else {
			runNormal();
		}
	}

	/**
	 * Process the IO interrupt, setting up the correct process if necessary.
	 * @param the_interrupt is the IO Interrupt that was thrown.
	 */
	private void doIO(final Interrupt the_interrupt) {
		final IODevice device = the_interrupt.getDevice();
		if (device != null) { // not keyboard interrupt
			device.nextToService().setStatus(Status.WAITING);
			my_scheduler.unBlockProcess(device.nextToService());
			if (my_IO_unbuffered) { // switch to process waiting on IO
				Main.out().println("Usurping " + my_current_process + 
						" in favor of " + device.nextToService() + ".");
				device.nextToService().run();
				Main.out().println("Returning to " + my_current_process);
			}
		}
		getInterrupt(); // remove current interrupt
	}

	/**
	 * Switches the current process to either the given process or the next waiting
	 * process, if passed a null.
	 * @param the_process is the next process to start, or if null the scheduler 
	 * gives the next process.
	 */
	private void switchProcess(final Process the_process) {
		if (my_current_process != null) {
			Main.out().println("Setting " + my_current_process + " to waiting.");
			my_current_process.setStatus(Status.WAITING);
			my_scheduler.pushProcess(my_current_process);
		} else {
			Main.out().println("No Process is currently running, nothing to stop.");
		}
		if (the_process != null) {
			my_current_process = the_process;
		} else {
			my_current_process = my_scheduler.getNextProcess();
		}
		Main.out().println("Starting " + my_current_process + ".");
	}

	/**
	 * Runs one cycle for the timer, which could cause an interrupt.
	 */
	private void timerRunOne() {
		my_timer_left--;
		if (my_timer_left == 0) {
			addInterrupt(new Interrupt());
			my_timer_left = my_timer_switch_iterations;
		}
	}

	/**
	 * Runs one cycle of my_current_process.
	 */
	private void runNormal() {
		if (my_current_process != null) {
			final SysCall call = my_current_process.run();
			if (my_current_process.getStatus() != Status.RUNNING) {
				currentNotRunning();
			}
			if (call == SysCall.DONE) {
				closeOutProcess();
			}
		} else if (my_scheduler.getNumberWaiting() > 0) {
			Main.out().println("Ending idle CPU, starting waiting process.");
			switchProcess(null); // get next process to run
		} else {
			Main.out().println("Out of waiting processes to run!");
		}
	}
	
	/**
	 * Called when my_current_process has been set to a status other than 
	 * running.  Checks status and updates appropriately.
	 */
	private void currentNotRunning() {
		if (my_current_process.getStatus() == Status.BLOCKED) {
			my_scheduler.blockProcess(my_current_process);
			Main.out().println(my_current_process + " is being blocked.");
		}
		my_current_process = my_scheduler.getNextProcess();
		Main.out().println("Starting " + my_current_process + ".");
	}

	/**
	 * Called when my_current_process is done.  Closes out all references to
	 * my_current_process and starts another process.
	 */
	private void closeOutProcess() {
		Main.out().println(my_current_process + " Finished, stopping.");
		my_scheduler.removeProcess(my_current_process);
		my_current_process = my_scheduler.getNextProcess();
		if (my_current_process != null) {
			Main.out().println("Starting " + my_current_process);
		}
	}

	/**
	 * Allows an external Interrupt to be given to the CPU.
	 * @param the_interrupt is the Interrupt to be given to the CPU.
	 */
	public synchronized void addInterrupt(final Interrupt the_interrupt) {
		if (the_interrupt.getType() == Interrupt.TIMER) {
			my_timer_interrupt = true;
		} else {
			my_io_interrupts.add(the_interrupt);
		}
	}
	
	/**
	 * If any interrupts have been given to the CPU, gets the oldest.  Otherwise 
	 * returns null.
	 * @return the oldest Interrupt waiting for service, null if none.
	 */
	private synchronized Interrupt getInterrupt() {
		if (my_io_interrupts.size() > 0) {
			return my_io_interrupts.remove();
		}
		return null;
	}

	/**
	 * Peeks at the next interrupt in the queue without removing it.
	 * @return the next Interrupt that would be returned by getInterrupt().
	 */
	private synchronized Interrupt peekInterrupt() {
		if (my_io_interrupts.size() > 0) {
			return my_io_interrupts.peek();
		}
		return null;
	}
}
